package com.example.demo.compile;

import com.example.demo.util.CommandUtil;
import com.example.demo.util.FileUtil;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

// 每次的 编译+运行 这个过程，就称为是一个 Task
public class Task {
    // 通过一组常量来约定临时文件的名字
    // 约定临时文件所在的共同目录
    private String WORK_DIR;
    // 约定代码的类名
    private String CLASS;
    // 约定要编译的代码文件名
    private String CODE;
    // 约定存放编译错误信息的文件名
    private String COMPILE_ERROR;
    // 约定存放运行时的标准输出的文件名
    private String STDOUT;
    // 约定存放运行时的标准错误的文件名
    private String STDERR;

    public Task(){
        // 在 Java 中使用 UUID 这个类就能生成一个 UUID
        WORK_DIR = "./tmp/" + UUID.randomUUID().toString() + "/";
        CLASS = "Solution";
        CODE = WORK_DIR + "Solution.java";
        COMPILE_ERROR = WORK_DIR + "compileError.txt";
        STDOUT = WORK_DIR + "stdout.txt";
        STDERR = WORK_DIR + "stderr.txt";
    }

    // 该方法表示编译运行
    // 参数：要编译运行的 Java 源代码
    // 返回值：表示编译运行的结果
    public Answer compileAndRun(Question question) {
        Answer answer = new Answer();
        // 0. 准备好用来存放临时文件的目录
        File workDir = new File(WORK_DIR);
        // 当目录不存在就去创建该目录
        if (!workDir.exists()) {
            // 创建多级目录
            workDir.mkdirs();
        }
        // 进行安全判断
        if(!checkCodeSafe(question.getCode())){
            System.out.println("用户提交了不安全的代码！");
            answer.setError(3);
            answer.setReason("您提交的代码可能会危害到服务器，禁止运行！");
            return answer;
        }

        // 1. 把 question 中的 code 写入到一个 .java 文件中（约定类名为 Main）
        FileUtil.writeFile(CODE, question.getCode());

        // 2. 创建子进程，调用 javac 进行编译
        //    如果编译出错，javac 就会把错误信息写入到 stderr 中（约定文件名为 compileError.txt）
        String compileCmd = String.format("javac -encoding utf8 %s -d %s", CODE, workDir);
        CommandUtil.run(compileCmd, null, COMPILE_ERROR);
        //     如果编译出错，编译错误信息就会保存到 COMPILE_ERROR 文件中，如果没有错误，该文件就是空的
        String compileError = FileUtil.readFile(COMPILE_ERROR);
        System.out.println(compileError);
        //     编译出错
        if (!compileError.equals("")) {
            answer.setError(1);
            answer.setReason(compileError);
            return answer;
        }
        //    编译正确

        // 3. 创建子进程，调用 java 命令进行运行
        //    运行程序是，会把 java 子进程的标准输出和标准错误获取到（stdout.txt，stderr.txt）
        String runCmd = String.format("java -classpath %s %s", workDir, CLASS);
        CommandUtil.run(runCmd, STDOUT, STDERR);
        String runError = FileUtil.readFile(STDERR);
        // 运行出错
        if (!runError.equals("")) {
            answer.setError(2);
            answer.setReason(runError);
            return answer;
        }
        // 运行正确


        // 4. 父进程获取到刚才的编译运行的结果，并打包成 Answer 对象
        answer.setError(0);
        answer.setStdout(FileUtil.readFile(STDOUT));
        return answer;
    }

    private boolean checkCodeSafe(String code) {
        List<String> blackList = new ArrayList<>();
        // 防止提交的代码运行恶意程序
        blackList.add("Runtime");
        blackList.add("exec");
        // 禁止提交的代码读写文件
        blackList.add("java.io");
        // 禁止提交的代码访问网络
        blackList.add("java.net");

        for(String targer : blackList){
            int pos = code.indexOf(targer);
            if(pos >= 0){
                return false;
            }
        }
        return true;
    }
}
